package de.upb.aquarium.api.shader;

import java.io.File;

import com.sun.opengl.cg.CGcontext;
import com.sun.opengl.cg.CGparameter;
import com.sun.opengl.cg.CGprogram;
import com.sun.opengl.cg.CgGL;

/**
 * This is an abstraction of a Cg shader. This class is intended to be used
 * as follows: Create a new shader, supplying the following information:
 * <ul>
 * 	<li>The program file,</li>
 * 	<li>the context this shader belongs to and</li>
 * 	<li>the profile of this shader, i.e. if it should be a vertex shader or
 * 		a fragment shader or some totally different type.</li>
 * </ul>
 * After the shader is created, you can use it. Every use should look as
 * follows:
 * <ol>
 * 	<li>Set the parameters of the shader. This has to be done by directly
 * 		using the CgGL functions, since encapsulation would be too much
 * 		effort. You can get the parameters by calling the provided
 * 		methods.</li>
 * 	<li>Activate the shader by calling <code>activate()</code></li>
 * 	<li>Render</li>
 * 	<li>Deactivate the shader by calling <code>deactivate()</code>. Don't leave this
 * 		out, even if activating another shader deactivates the previously
 * 		activated one automagically. 
 * </ol>
 * Setting the parameters could be done in a derived class in a overwritten
 * <code>activate()</code> method. 
 * @author Jost
 *
 */
public class CgShader {

	private CGcontext context;
	private CGprogram program;
	private int profile;
	
	/**
	 * Create a new shader from a program file.
	 * @param context The context the program belongs to.
	 * @param programFile The file containing the program code.
	 * @param profile The profile to be used. Use
	 * 		<ul>
	 * 			<li>CgGL.CG_GL_VERTEX for a vertex shader,</li>
	 * 			<li>CgGL.CG_GL_FRAGMENT for a fragment shader.</li>
	 * 		</ul>
	 * 		There are more possible values, see the Cg User Manual.
	 * @throws RuntimeException if anything goes wrong.
	 */
	public CgShader(File programFile,
					CGcontext context,
					int profile) throws RuntimeException {
		
		this.context = context;
		
		// If this is a vertex or fragment shader, the cg lib provides
		// to search for the newest shader profiles. For other profiles
		// this is not possible, so use them directly.
		if ((profile != CgGL.CG_GL_FRAGMENT)
		 || (profile != CgGL.CG_GL_VERTEX))
		{
			this.profile = CgGL.cgGLGetLatestProfile(profile);
		}
		else
		{
			this.profile = profile;
		}
		
		// Set the optimal parameters for compiling the program. This
		// is appended to the normal parameters given to the compiler.
		CgGL.cgGLSetOptimalOptions(this.profile);
		program = CgGL.cgCreateProgramFromFile(
				this.context,
				CgGL.CG_SOURCE,	// The program is given as source code
				programFile.getAbsolutePath(),
				this.profile,
				null,	// The entry point shall be "main", the default value
				null);	// There are no compiler arguments (except those from
						// the call to cgGLSetOptimalOptions above)
		
		CgShader.checkForErrors();
		
		CgGL.cgGLLoadProgram(program);
	
		CgShader.checkForErrors();
	}
	
	/**
	 * Sets this as the active shader to use. After this call, the
	 * shader has is loaded and bound.
	 * 
	 * @throws RuntimeException
	 * 		If something has gone wrong.
	 */
	public void activate()
	{
		assert(CgGL.cgGLIsProgramLoaded(program));
		
		CgGL.cgGLEnableProfile(profile);
		CgGL.cgGLBindProgram(program);
	}
	
	/**
	 * Deactivate this shader.
	 * 
	 * @throws RuntimeException
	 * 		If something has gone wrong.
	 */
	public void deactivate()
	{
		CgGL.cgGLDisableProfile(profile);
		CgShader.checkForErrors();
	}
	
	/**
	 * Returns the global parameter (i.e. the parameter in file scope) of the
	 * associated Cg program with the given name.
	 * @param name The parameter to return.
	 * @return The requested parameter. 
	 * @throws IllegalArgumentException if there is no parameter with the given
	 * 		name.
	 * @throws RuntimeException if something else goes wrong.
	 */
	public CGparameter getGlobalParameter(String name)
							throws IllegalArgumentException,
								   RuntimeException
	{
		CGparameter parameter = CgGL.cgGetNamedProgramParameter(
				program,
				CgGL.CG_GLOBAL,
				name);

		if (parameter == null)
		{
		throw new IllegalArgumentException(
						"The requested parameter does not exist.");
		}
		
		CgShader.checkForErrors();
		return parameter;
	}
	
	/**
	 * Returns the global parameter (i.e. the parameter of the programs entry
	 * point) of the associated Cg program with the given name.
	 * @param name The parameter to return.
	 * @return The requested parameter. 
	 * @throws IllegalArgumentException if there is no parameter with the given
	 * 		name.
	 * @throws RuntimeException if something else goes wrong.
	 */
	public CGparameter getProgramParameter(String name)
							throws IllegalArgumentException,
							   	   RuntimeException
	{
		CGparameter parameter = CgGL.cgGetNamedProgramParameter(
									program,
									CgGL.CG_PROGRAM,
									name);
		
		if (parameter == null)
		{
			throw new IllegalArgumentException(
									"The requested parameter does not exist.");
		}
		
		CgShader.checkForErrors();
		return parameter;
	}
	
	/**
	 * Checks if an error has occurred. If so, a RuntimeException is thrown.
	 * Also, by every call to this method the errors are reset. So if a first
	 * call found an error, the second subsequent call won't find any errors,
	 * unless there happened more errors between the calls.  
	 * @throws RuntimeException In case of an error. Contains a detailed error
	 * 		description.
	 */
	public static void checkForErrors() throws RuntimeException
	{
		int errorCode = CgGL.cgGetError();
		if (errorCode != 0)
		{
			String errorString = CgGL.cgGetErrorString(errorCode);
			throw new RuntimeException("An error occured while using the "
									 + "Cg Api. The message was "
									 + errorString);
		}
		
		// Flush error codes (for safety)
		CgShader.flushErrors();
	}
	
	/**
	 * Resets the error codes, so subsequent calls to checkForErrors() don't
	 * find any errors. Call every time a error might have occured (i.e. after
	 * every call to the Cg API).
	 */
	public static void flushErrors()
	{
		// Fetch the latest error codes, so it is reset to zero.
		CgGL.cgGetError();
		CgGL.cgGetFirstError();
	}
}
